﻿using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using HOPETechnik;
using System.IO;
using System.Collections.ObjectModel;
using System.ComponentModel;
using HOPETechnik.XMLParser;

namespace TestSocket
{
    /// <summary>
    /// Interaction logic for AcceptedSockets.xaml
    /// </summary>
    public partial class AcceptedSockets : UserControl, IPanel, ISocketEventsCallback
    {
        class SocketInfo : INotifyPropertyChanged
        {
            string m_strLocalAddr;
            string m_strRemoteAddr;
            public bool bBinary;
            CAsyncSocket me;            
            long m_FileSize = 0;
            long m_bytesLeft = 0;

            public long BytesLeft 
            { 
                get { return m_bytesLeft; }
                set 
                { 
                    m_bytesLeft -= value;                    
                }
            }

            public long filesize
            {                
                set 
                {
                    m_FileSize = value;
                    m_bytesLeft = value;
                }
            }  

            public double Progress
            {
                get{ return ((m_FileSize - m_bytesLeft) * 100.0) / (double)m_FileSize; }
                set { OnPropertyChanged("progress"); }
            }            

            public SocketInfo(CAsyncSocket sock)
            {
                me = sock;
                m_strLocalAddr = sock.LocalInfo;
                m_strRemoteAddr = sock.PeerInfo;                
            }

            public CAsyncSocket myself { get { return me; } }
            public string SrcAddress { get { return m_strLocalAddr; } }
            public string DestAddress { get { return m_strRemoteAddr; } }
            public Visibility CanSendFile
            {
                get 
                {
                    if (bBinary)
                        return Visibility.Visible;
                    else
                        return Visibility.Hidden;
                }
            }
            public Visibility CanSendMsg
            {
                get 
                {
                    if (bBinary)
                        return Visibility.Hidden;
                    else
                        return Visibility.Visible;
                }
            }

            public event PropertyChangedEventHandler PropertyChanged;
            public void OnPropertyChanged(string strPropName)
            {
                PropertyChangedEventHandler handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(strPropName));
                }
            }
        }
        Dictionary<string, BinaryWriter> m_binarySockets;
        Dictionary<string, StringBuilder> m_Sockets;        
        CAsyncSocket m_msgSocket;
        CAsyncSocket m_binSocket;
        MainWindow m_wnd;
        ObservableCollection<SocketInfo> m_AcceptedSockets;        
        string m_strMsgType;
        string m_strBinType;
        CRequestFactory m_requests;        
        CXMLParser m_parser;

        void UpdateSocketList(CAsyncSocket sock, bool bBinary,  bool bAdd)
        {
            if (bAdd)
            {
                SocketInfo info = new SocketInfo(sock);
                info.bBinary = bBinary;
                m_AcceptedSockets.Add(info);
            }
            else
            {
                foreach(SocketInfo info in m_AcceptedSockets)
                {
                    if (info.myself==sock)
                    {
                        m_AcceptedSockets.Remove(info);
                        break;
                    }
                }
            }
        }

        public AcceptedSockets(MainWindow wnd)
        {
            InitializeComponent();
            m_msgSocket = new CAsyncSocket(this);
            m_binSocket = new CAsyncSocket(this);
            m_Sockets = new Dictionary<string, StringBuilder>();
            m_binarySockets = new Dictionary<string, BinaryWriter>();
            m_wnd = wnd;
            m_AcceptedSockets = new ObservableCollection<SocketInfo>();
            SocketList.ItemsSource = m_AcceptedSockets;
            m_strMsgType = string.Format(":" + m_wnd.strMsgPort);
            m_strBinType = string.Format(":" + m_wnd.strBinaryPort);
            m_requests = new CRequestFactory();
            m_parser = new CXMLParser();
        }

        public void OnActivate()
        {
            m_msgSocket.StartListening(int.Parse(m_wnd.strMsgPort), m_wnd.strSrcIP);
            m_binSocket.StartListening(int.Parse(m_wnd.strBinaryPort), m_wnd.strSrcIP);
            m_wnd.dprintf("Listening...");
        }

        public void OnDeactivate(){}        

        public void OnAppClosing()
        {
            if (m_AcceptedSockets.Count>0)
            {
                foreach (SocketInfo info in m_AcceptedSockets)
                {
                    info.myself.Disconnect();
                    info.myself.Dispose();
                }
            }
            m_msgSocket.Dispose();
            m_binSocket.Dispose();
        }

        public void OnSocketAccepted(CAsyncSocket acceptedSocket)
        {
            m_wnd.dprintf("accepted connection from " + acceptedSocket.PeerInfo);            
            bool binary = false;
            
            if (acceptedSocket.LocalInfo.IndexOf(m_strMsgType)>-1)
            {
                m_Sockets[acceptedSocket.PeerInfo] = new StringBuilder();
                m_wnd.dprintf("accepted msg socket from " + acceptedSocket.PeerInfo);                
            }
            else if (acceptedSocket.LocalInfo.IndexOf(m_strBinType)>-1)
            {
                m_binarySockets[acceptedSocket.PeerInfo] = null;
                m_wnd.dprintf("accepted binary socket from " + acceptedSocket.PeerInfo);
                binary = true;
            }
            Dispatcher.Invoke(new System.Action(() => UpdateSocketList(acceptedSocket,binary, true)));
        }

        public void OnSocketConnected(CAsyncSocket sock){}        

        public void OnSocketDisconnected(CAsyncSocket sock)
        {
            Dispatcher.Invoke(new System.Action(() => UpdateSocketList(sock, false, false)));
            
            if (sock.LocalInfo.IndexOf(m_strMsgType)>-1)
            {
                m_Sockets.Remove(sock.PeerInfo);                
                m_wnd.dprintf(sock.PeerInfo+" disconnected");
            }
            else if (sock.LocalInfo.IndexOf(m_strBinType)>-1)
            {
                m_binarySockets.Remove(sock.PeerInfo);
                m_wnd.dprintf(sock.PeerInfo + " disconnected");
            }
            UpdateSocketList(sock, false, false);
            sock.Dispose();                                    
        }

        public void OnSocketDataReceived(CAsyncSocket sock)
        {                        
            if (sock.LocalInfo.IndexOf(m_strMsgType)>-1)
            {
                string str = Encoding.UTF8.GetString(sock.rcvBuffer.buffer, 0, sock.rcvBuffer.nPayloadSize);
                m_Sockets[sock.PeerInfo].Append(str);
                if (str.IndexOf(CAsyncSocket.strEndingDelimiter) > -1)
                {
                    str = m_Sockets[sock.PeerInfo].ToString();
                    str = str.Substring(0, str.IndexOf(CAsyncSocket.strEndingDelimiter));
                    System.Diagnostics.Debug.Write(str);
                    //fully formed, get the request flyweight
                    CBaseRequest req = m_requests.getRequest(str);
                    if (req != null)
                    {
                        m_parser.setXMLStream(req);
                        m_parser.Parse();

                        if (req.GetType() == typeof(CTextMessage))
                            m_wnd.dprintf(sock.PeerInfo + ">> " + ((CTextMessage)req).strMessage);
                        else if (req.GetType()==typeof(CFileSendRequest))
                        {
                            CFileSendRequest Req=(CFileSendRequest)req;
                            if (PrepareFileStream(Req.strSocketAddr, Req.m_strFileName))
                            {
                                Dispatcher.Invoke(new System.Action(() => UpdateFileTotalSize(Req.strSocketAddr, Req.m_lFileSize)));
                                req = m_requests.getRequest("<FileSendResp>");
                                ((CFileSendResp)req).bReady2Recv = true;
                                SendMessage(req.toXML(), sock);
                                m_wnd.dprintf("starting file data receiving");
                            }
                        }                        
                    }
                    m_Sockets[sock.PeerInfo].Clear();
                }
            }
            else
            {
                m_binarySockets[sock.PeerInfo].Write(sock.rcvBuffer.buffer, 0, sock.rcvBuffer.nPayloadSize);
                Dispatcher.Invoke(new System.Action(() => UpdateFileRcvProgress(sock)));
            }
        }

        void UpdateFileRcvProgress(CAsyncSocket sock)
        {
            foreach (SocketInfo info in m_AcceptedSockets)
            {
                if (info.DestAddress == sock.PeerInfo)
                {
                    info.BytesLeft=sock.rcvBuffer.nPayloadSize;
                    info.Progress = 0;//just trigger the update
                    if (info.BytesLeft == 0)
                        CloseBinaryWriter(sock.PeerInfo);
                    break;
                }
            }
        }
        void UpdateFileTotalSize(string strAddr, long lSize)
        {
            foreach (SocketInfo info in m_AcceptedSockets)
            {
                if (info.DestAddress == strAddr)
                {
                    info.filesize = lSize;                                      
                    break;
                }
            }
        }
        void CloseBinaryWriter(string strSrc)
        {
            if (m_binarySockets.ContainsKey(strSrc))
            {
                m_binarySockets[strSrc].Flush();
                m_binarySockets[strSrc].Close();
                m_binarySockets[strSrc].Dispose();
                m_wnd.dprintf("Binary writer for " + strSrc + " closed");
            }
        }
        bool PrepareFileStream(string strSrc, string strFileName)
        {
            bool bSuccess=false;
            Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
            dlg.FileName = strFileName;
            if(dlg.ShowDialog()==true)
            {
                //prepare the binary stream here 
                if (m_binarySockets.ContainsKey(strSrc))
                {
                    m_binarySockets[strSrc] = new BinaryWriter(File.Open(dlg.FileName, FileMode.Create));
                    bSuccess = true;
                }
            }
            return bSuccess;
        }

        public void OnSocketDataSent(CAsyncSocket sock)
        {
            m_wnd.dprintf(" Ready to send to"+sock.PeerInfo);
        }

        public void ReportError(CAsyncSocket sock, System.Net.Sockets.SocketException ex)
        {
            m_wnd.dprintf("Error>>" + ex.ToString());
            if (ex.ErrorCode == 0x80004005)            
                OnSocketDisconnected(sock);            
        }

        void SendMessage(string strMsg, CAsyncSocket sock)
        {
            string s = strMsg + CAsyncSocket.strEndingDelimiter;
            byte[] data = Encoding.UTF8.GetBytes(s);
            int nLength = data.Length;
            if (sock.sendBuffer.buffer.Length < nLength)
            {
                m_wnd.dprintf("Error, msg too long!");
                return;
            }
            System.Buffer.BlockCopy(data, 0, sock.sendBuffer.buffer, 0, nLength);
            sock.sendBuffer.nPayloadSize = nLength;
            //sock.sendBuffer.nBytesLeft = nLength;
            sock.SendData();            
        }

        private void OnSendMsg(object sender, RoutedEventArgs e)
        {
            Button btn = (Button)sender;
            CAsyncSocket sock = (CAsyncSocket)btn.Tag;
            CTextMessage req = (CTextMessage)m_requests.getRequest("<TextMessage>");
            if (req != null)
            {
                req.strMessage = tbMsg.Text;
                SendMessage(req.toXML(), sock);
            }
        }

        private void OnDisconnect(object sender, RoutedEventArgs e)
        {
            Button btn = (Button)sender;
            CAsyncSocket sock = (CAsyncSocket)btn.Tag;
            sock.Disconnect();
            UpdateSocketList(sock, false, false);
        }
        public void OnSocketFileSent(CAsyncSocket sock){}        
    }
}
